// ---------------  异步相关知识点  -----------------开始
// 说一下事件循环机制
{}

// 怎么判断是否是promise类型？有几种方式
{
  function promiseFn(params) {
    return new Promise(() => {});
  }
  // 以下几种方式，通过实例指向，或是
  console.log(promiseFn() instanceof Promise);
  console.log(promiseFn().constructor === Promise);
  console.log('then' in promiseFn() || 'finally' in promiseFn());
  console.log(Object.prototype.toString.call(promiseFn())); // 输出 [object Promise]

}

// 说一说promise，有几个状态，通过catch捕获到reject之后，在catch后面还能继续执行then方法嘛，如果能执行执行的是第几个回调函数
{
  const test = function() {
    return new Promise(function(resolve,reject){
      reject('失败后运行的，也会被catch捕获到')
    });
  }
  // 执行异步
  test().then((v)=>{
    console.log('成功');
  }).catch((e)=>{
    console.log('eeee');
    return 'a';
  }).then((v)=>{
    console.log('vvv');
    return 'v';
  }).then((v)=>{
    console.log('v2');
  }).then((v)=>{
    console.log('v3');
  })

  /* 回答：promise有三种状态：fulfilled(已成功)/pengding(进行中)/rejected(已拒绝)
  通过catch捕获到reject之后，在catch后面还能继续执行then方法，可以无限一直往下执行.then方法，只有当出现错误时才会停止.then往下运行。
   */
}

// Event Loop 是 JavaScript 异步编程的核心思想，下面代码打印顺序是？
{
  async function async1() {
    console.log('async1 start');
    await async2();
    console.log('async1 end');
  }
  async function async2() {
    console.log('async2');
  }
  console.log('script start');
  setTimeout(function() {
    console.log('setTimeout');
  }, 0);
  async1();
  new Promise(function(resolve) {
    console.log('promise1');
    resolve();
  }).then(function() {
    console.log('promise2');
  });
  console.log('script end');

  /* 
    script start
    async1 start
    async2
    promise1
    script end
    async1 end
    promise2
    setTimeout
  */
}


// 有多个请求接口，如何做到按我只能上限三个异步请求接口，只有等其中一个或多个结束后，在加入新的后续接口进来，来现实同时请求异步上限数量的问题。
{
  var urls = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20];
  const limit = 5;

  function sendRequest(urls, limit, callback) {
    function _send(urls) {
      return new Promise((resolve, reject) => {
        const url = urls.shift();
        if (url) {
          setTimeout(() => {
            console.log(`当前发送：${url}`);
            resolve(url);
          }, 1000);
        }
      }).finally(() => {
        if (urls.length > 0) {
          return _send(urls);
        }
      });
    }
    let asyncList = [];
    while (limit--) {
      asyncList.push(_send(urls));
    }
    return Promise.all(asyncList).then(callback);
  }

  sendRequest(urls, limit, function(list) {
    console.log('all urls sended!', list);
  });
}
{
  // 自己实现方案2
  let urls = []; // 请求Fn堆
  for (let i = 0; i <= 20; i++) {
    urls.push(fetchFn);
  }

  const limit = 4;
  let onLimit = 0; // 当前请求数量
  queue();

  // 长轮询
  function queue() {
    let time = setInterval(() => {
      if (limit > onLimit) {
        _send(urls);
      }

      if (urls.length == 0) clearInterval(time); // 测试用，中断轮询
    }, 100);
  }

  function _send(urls) {
    return new Promise((resolve, reject) => {
      const url = urls.shift();
      onLimit++;
      if (url) {
        setTimeout(async () => {
          // mock请求接口
          const res = await url(urls.length);
          console.log(`当前发送：${res.data.version}`);
          // console.log(`当前发送：${url}`);
          resolve(url);
        }, 0);
      }
    }).finally(() => {
      if (urls.length > 0) {
        onLimit--;
      }
    });
  }

  async function fetchFn(num) {
    return fetch(
      `https://www.fastmock.site/mock/f29153ea802b2006237d862f7a54b82b/base/test?version=${num}`,
      {
        method: 'get',
        mode: 'cors',
        headers: {'Content-Type': 'application/x-www-form-urlencoded'}
      }
    ).then(res => {
      result = res.json();
      return result;
    });
  }
}

// 1、基于Promise的锁实现
// 考虑在单进程单线程下的锁实现，基于下面的代码片段实现 LockService 类
{
  // https://www.yuque.com/docs/share/75b3ca8c-3fe1-4acd-a6b2-b681a1611f6b?#
  class LockService {
    // key: string
    async lockByKey(key) {
      return this;
    }

    release(){ }
  }
  function delayFn(ms) {
    return new Promise(resolve => setTimeout(resolve, ms))
  }
  async function process(key) {
    // 同一个key会被锁住，不同的key不受影响
    let locker = await lockService.lockByKey(key)
    console.log('get locker', key)
    try {
      // 停留1s
      await delayFn(1000)
    } finally {
      console.log('end locker', key)
      locker.release()
    }
  }
  
  process('key1')
  process('key1')
  process('key2')
  
  // get locker key1
  // get locker key2
  // 1s后
  // end locker key1
  // end locker key2
  // get locker key1
  // 1s后
  // end locker key1

  // 解答区域
  interface IDelayFn {
    resolve: Function,
    key: string,
  }
  class LockService {
    hasMap = new Map();
    delayFn: IDelayFn[] = [];

    async lockByKey(key: string) {
      if (this.hasMap.has(key)) {
        return new Promise<LockService>((resolve, reject) => {
          this.delayFn.push({
            key,
            resolve,
          });
        });
      }
      this.hasMap.set(key, key);
      return this;
    }

    // 解锁
    release() {
      const delayShift = this.delayFn.shift();
      if (delayShift && this.hasMap.has(delayShift.key)) {
        setTimeout(() => {
          delayShift.resolve(this);
        });
      }
    }
  }
  const lockService = new LockService();
}

// 2、基于Promise的执行数量控制
{
  class PromiseLimit<T> {
    constructor(private limit: number) {}
    
    waitForFree() {}
    
    runTask(task: () => Promise<T>){}
  }
  
  function delayFn(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }
  async function main() {
    let limit = new PromiseLimit<void>(3);
    for (let i = 0; i < 1000; i++) {
      await limit.waitForFree();
      limit.runTask(async () => {
        /// 耗时任务，PromiseLimit会保证最多同时运行三个，第四个会被卡在waitForFree里
        /// 一旦有任务完成，后续任务自动执行
        await delayFn(Math.random() * 10000); // 模拟随机耗时
        console.log(`执行中>>>${i}`);
      });
    }
  }
  main();

  // 实现逻辑版本，此版本有点缺陷就是一定要同时几个队列上限一起完成后才会后续的
  interface IrunTaskArr<T> {
    resolve: Function,
    task: () => Promise<T>,
  }
  
  interface ItempRunTask<T> {
    resolves: Function[],
    tasks: Promise<T>[]
  }
  
  class PromiseLimit<T> {
    runTaskArr: (IrunTaskArr<T>)[];
    onlineLimit: number;
    tempRunTask: ItempRunTask<T>;
  
    constructor(private limit: number) {
      this.limit = limit; // 同时请求上限
      this.onlineLimit = 0; // 正在执行数量
      this.runTaskArr = []; // 队列堆
      this.tempRunTask = {
        resolves: [],
        tasks: [],
      };
    }
  
    // 释放指定数量队列，可被执行runTask
    waitForFree() {
      const _limit = this.limit;
      const _runTaskArr = this.runTaskArr;
  
      // 加入准备执行队列中
      const oneRunTaskRes = _runTaskArr.shift();
      if (oneRunTaskRes) {
        this.tempRunTask.tasks.push(oneRunTaskRes.task());
        this.tempRunTask.resolves.push(oneRunTaskRes.resolve);
      }
      // 队列上限处理
      if (this.onlineLimit >= _limit) {
        return Promise.all(this.tempRunTask.tasks).finally(() => {
          this.tempRunTask.resolves.forEach((itemResolve) => {
            itemResolve();
          });
          console.log('成功批量执行runTask');
        });
      }
  
      return; // 未到上限数量 不等待卡住
    }
  
    runTask(task: () => Promise<T>) {
      // 单个耗时任务标记，开始--结束
      new Promise((resolve) => {
        this.onlineLimit++;
        this.runTaskArr.push({
          task: task,
          resolve,
        });
      }).finally(() => {
        console.log('单个task完成');
        this.onlineLimit--;
      });
    }
  }
  
  async function main() {
    let limit = new PromiseLimit<void>(3)
    for (let i = 0; i < 100; i++) {
      await limit.waitForFree()
      limit.runTask(async () => {
        /// 耗时任务，PromiseLimit会保证最多同时运行三个，第四个会被卡在waitForFree里
        /// 一旦有任务完成，后续任务自动执行
        const time = Math.random() * 10000;
        return new Promise((resolve, reject) => {
          // 随机耗时
          setTimeout(() => {
            console.log('中间层', i);
            resolve();
          }, time);
        });
      })
    }
  }
  main();

  // 实现完善版本，符合出题者逻辑，异步到上限在队列中，有请求完的立马列入队列执行
  class PromiseLimit<T> {
    queues: any[];
    curryLimit: number;
    lockResolve: (value?: T | PromiseLike<T>) => void;
    constructor(private limit: number) {
      this.limit = limit; // 队列上限
      this.queues = [];
      this.curryLimit = 0; // 当前队列数量
    }

    // 队列处理
    waitForFree() {
      if (this.curryLimit >= this.limit) {
        return new Promise((reslove) => {
          this.lockResolve = reslove;
        });
      }
    }

    // 异步耗时执行
    runTask(task: () => Promise<T>) {
      this.curryLimit++;
      task().finally(() => {
        this.curryLimit--;
        if (this.lockResolve) this.lockResolve();
      });
    }
  }

  function delayFn(ms) {
    return new Promise((resolve) => setTimeout(resolve, ms));
  }

  async function main() {
    let limit = new PromiseLimit<void>(3);
    for (let i = 0; i < 30; i++) {
      await limit.waitForFree();
      limit.runTask(async () => {
        /// 耗时任务，PromiseLimit会保证最多同时运行三个，第四个会被卡在waitForFree里
        /// 一旦有任务完成，后续任务自动执行
        await delayFn(Math.random() * 10000);
        console.log(`执行中>>>${i}`);
      });
    }
  }
  main();
}

// 以下代码中输出的结果是什么？为什么
{
  async function async1(){
    console.log('async1 start');
    await async2();
    console.log('async1 end');
  }

  async function async2(){
    console.log('async2');
  }

  console.log('script start');
  setTimeout(() => {
    console.log('setTimeout');
  }, 0);

  async1();

  new Promise((resolve)=>{
    console.log('promise1');
    resolve();
  }).then(()=>{
    console.log('promise2');
  });
  console.log('script end');
}

// 输出结果是什么？为什么？
{
  Promise.resolve(1)
  .then(2)
  .then(Promise.resolve(3))
  .then((res) => console.log(res));

  // 输出的1，因为promise执行了resolve成功，后面的.then和在次执行resolve是不会被传递下去的，所以最终then接收到的值是1
}

// Promise.all执行顺序和返回顺序是什么样的
{
  /* 
  在Promise.all中执行顺序是同步的，即按顺序[p1,p2,p3]执行内部函数
  而执行后内部运行还是异步线程，所以返回的是谁先请求结束resolve或reject，谁就先返回
  */
}



